/***************************************************************************************
* Copyright (c) 2014-2022 Zihao Yu, Nanjing University
*
* NEMU is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*          http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
*
* See the Mulan PSL v2 for more details.
***************************************************************************************/

#include "mmu.h"
#include "sim.h"
#include <cstdint>

#define __EXPORT __attribute__((visibility("default")))

#define MEM_SIZE 0x10000000ul

#undef DEFAULT_ISA

#ifdef ISA_riscv32
#define DEFAULT_ISA "RV32IM"
typedef uint32_t word_t ;
typedef int32_t sword_t ;
#else
#define DEFAULT_ISA "RV64IM"
typedef uint64_t word_t ;
typedef int64_t sword_t ;
#endif

#define PRIV_MODE_M "M"

enum { DIFFTEST_TO_DUT, DIFFTEST_TO_REF };

static std::vector<std::pair<reg_t, abstract_device_t*>> difftest_plugin_devices;
static std::vector<std::string> difftest_htif_args;
static std::vector<std::pair<reg_t, mem_t*>> difftest_mem(
        1, std::make_pair(reg_t(DRAM_BASE), new mem_t(MEM_SIZE)));
static std::vector<int> difftest_hartids;
static debug_module_config_t difftest_dm_config = {
    .progbufsize = 2,
    .max_sba_data_width = 0,
    .require_authentication = false,
    .abstract_rti = 0,
    .support_hasel = true,
    .support_abstract_csr_access = true,
    .support_abstract_fpr_access = true,
    .support_haltgroups = true,
    .support_impebreak = true
};


struct DifftestContext {
    word_t* gpr;
    word_t* pc;
    word_t* csr;
    DifftestContext(word_t* pc_ptr, word_t* gpr_ptr, word_t* csr_ptr) {
        this->gpr = gpr_ptr;
        this->pc = pc_ptr;
        this->csr = csr_ptr;
    }
    DifftestContext() {
        this->gpr = new word_t[32];
        this->pc = new word_t;
        this->csr = new word_t[4096];
    }
    ~DifftestContext() {
        delete[] this->gpr;
        delete this->pc;
        delete[] this->csr;
    }
};

enum csr_addr {
    MSTATUS = 0x300,
    MIE = 0x304,
    MTVEC = 0x305,
    MEPC = 0x341,
    MCAUSE = 0x342,
    MIP = 0x344,
};

static sim_t* s = NULL;
static processor_t *p = NULL;
static state_t *state = NULL;

void sim_t::diff_init(int port) {
    p = get_core("0");
    state = p->get_state();
}

void sim_t::diff_step(uint64_t n) {
    step(n);
}

void sim_t::diff_get_regs(void* diff_context) {
    struct DifftestContext* ctx = (struct DifftestContext*)diff_context;
    for (int i = 0; i < NXPR; i++) {
        ctx->gpr[i] = state->XPR[i];
    }
    *ctx->pc = state->pc;

    // get CSRs
    ctx->csr[MSTATUS] = state->mstatus->read();
	ctx->csr[MIE] = state->mie->read();
    ctx->csr[MTVEC] = state->mtvec->read();
    ctx->csr[MEPC] = state->mepc->read();
    ctx->csr[MCAUSE] = state->mcause->read();
	// ctx->csr[MIP] = state->mip->read();
}

void sim_t::diff_set_regs(void* diff_context) {
    struct DifftestContext* ctx = (struct DifftestContext*)diff_context;
    for (int i = 0; i < NXPR; i++) {
        state->XPR.write(i, (sword_t)ctx->gpr[i]);
    }
    state->pc = *ctx->pc;

    // set CSRs
	state->mstatus->write(ctx->csr[MSTATUS]);
	state->mie->write(ctx->csr[MIE]);
    state->mtvec->write(ctx->csr[MTVEC]);
    state->mepc->write(ctx->csr[MEPC]);
    state->mcause->write(ctx->csr[MCAUSE]);
	// state->mip->write(ctx->csr[MIP]);
}

void sim_t::diff_memcpy(reg_t dest, void* src, size_t n) {
    mmu_t* mmu = p->get_mmu();
    for (size_t i = 0; i < n; i++) {
        mmu->store<uint8_t>(dest+i, *((uint8_t*)src+i));
    }
}

extern "C" {

__EXPORT void difftest_memcpy(word_t addr, void *buf, size_t n, bool direction) {
    if (direction == DIFFTEST_TO_REF) {
        s->diff_memcpy(addr, buf, n);
    } else {
        assert(0);
    }
}

__EXPORT void difftest_regcpy(void* dut, bool direction) {
    if (direction == DIFFTEST_TO_REF) {
        s->diff_set_regs(dut);
    } else {
        s->diff_get_regs(dut);
    }
}

__EXPORT void difftest_exec(uint64_t n) {
    s->diff_step(n);
}

__EXPORT void difftest_init() {
    difftest_htif_args.push_back("");
    cfg_t cfg(/*default_initrd_bounds=*/std::make_pair((reg_t)0, (reg_t)0),
                        /*default_bootargs=*/nullptr,
                        /*default_isa=*/DEFAULT_ISA,
                        /*default_priv=*/PRIV_MODE_M,
                        /*default_varch=*/DEFAULT_VARCH,
                        /*default_misaligned=*/false,
                        /*default_endianness*/endianness_little,
                        /*default_pmpregions=*/16,
                        /*default_mem_layout=*/std::vector<mem_cfg_t>(),
                        /*default_hartids=*/std::vector<size_t>(1),
                        /*default_real_time_clint=*/false,
                        /*default_trigger_count=*/4);
    s = new sim_t(&cfg, false,
            difftest_mem, difftest_plugin_devices, difftest_htif_args,
            difftest_dm_config, nullptr, false, NULL,
            false,
            NULL,
            true);
    s->diff_init(0);
}

__EXPORT void difftest_raise_intr(uint64_t NO) {
    trap_t t(NO);
    p->take_trap_public(t, state->pc);
}

}
